13. Ansible Playbook with_X循环语句
目录
循环语句
简介
我们在编写playbook的时候,不可避免的要执行一些重复性操作,比如指安装软件包,批量创建用户,操作某个目录下的所有文件等。正如我们所说,ansible一门简单的自动化语言,所以流程控制、循环语句这些编程语言的基本元素它同样都具备。
在Ansible 2.5以前,playbook通过不同的循环语句以实现不同的循环,这些语句使用with_
作为前缀。这些语法目前仍然兼容,但在未来的某个时间点,会逐步废弃。
下面列出一些较常见的with_X
循环语句:
with_items
with_flattened
with_list
with_together
with_nested
with_indexed_items
with_sequence
with_random_choice
with_dict
with_subelement
with_file
with_fileglob
with_lines
1. with_items
简单的列表循环
场景一: 循环打印inventory中所有未分组的主机
- hosts: test gather_facts: no tasks: - debug: msg: "{{ item }}"" with_items: "{{ groups.ungrouped }}"
场景二: 直接在with_items中定义被循环的列表
- hosts: test gather_facts: no tasks: - name: "with_items" debug: msg: "{{ item }}" with_items: - "user0" - "user1" - "user2"
也可以写成如下方式:
- hosts: test gather_facts: no tasks: - name: "with_items" debug: msg: "{{ item }}" with_items: ["user0","user1","user2"]
场景三: 在with_items中定义更复杂的列表
- hosts: test gather_facts: no tasks: - name: "create directory" file: path: "/{{ item.path1 }}/{{ item.path2 }}" with_items: - { path1: a, path2: b} - { path1: c, path2: d}
2. with_list
与with_items
一样,也是用于循环列表。区别是,如果列表的值也是列表,with_items
会将第一层嵌套的列表拉平,而with_list
会将值作为一个整体返回。
示例:
# 使用with_items的示例 - hosts: test gather_facts: no tasks: - name: "with_items" debug: msg: "{{ item }}" with_items: - [1, 2] - [a, b] # 返回结果: # ansible-playbook with_items_test.yml PLAY [test] ***************************************************************************************************************************************************************** TASK [Gathering Facts] ****************************************************************************************************************************************************** ok: [10.1.61.187] ▽ASK [with_items] *********************************************************************************************************************************************************** ok: [10.1.61.187] => (item=1) => { "msg": 1 } ok: [10.1.61.187] => (item=2) => { "msg": 2 } ok: [10.1.61.187] => (item=a) => { "msg": "a" } ok: [10.1.61.187] => (item=b) => { "msg": "b" } PLAY RECAP ****************************************************************************************************************************************************************** 10.1.61.187 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 # 使用with_list的示例 - hosts: test gather_facts: no tasks: - name: "with_list" debug: msg: "{{ item }}" with_list: - [1, 2] - [a, b] # 返回结果: # ansible-playbook with_list_ex.yml PLAY [test] ***************************************************************************************************************************************************************** TASK [with_list] *********************************************************************************************************************************************************** ok: [10.1.61.187] => (item=[1, 2]) => { "msg": [ 1, 2 ] } ok: [10.1.61.187] => (item=['a', 'b']) => { "msg": [ "a", "b" ] } PLAY RECAP ****************************************************************************************************************************************************************** 10.1.61.187 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
3. with_flattened
with_flattened
与with_items
类似,当处理复杂的多级列表嵌套时,会将所有的列表全部拉平:
- hosts: test gather_facts: no tasks: - name: "with_flattened" debug: msg: "{{ item }}" with_flattened: - [1, 2,[3,4]] - [a, b]
返回结果:
# ansible-playbook with_flattened_ex.yml PLAY [test] ***************************************************************************************************************************************************************** TASK [with_flattened] *********************************************************************************************************************************************************** ok: [10.1.61.187] => (item=1) => { "msg": 1 } ok: [10.1.61.187] => (item=2) => { "msg": 2 } ok: [10.1.61.187] => (item=3) => { "msg": 3 } ok: [10.1.61.187] => (item=4) => { "msg": 4 } ok: [10.1.61.187] => (item=a) => { "msg": "a" } ok: [10.1.61.187] => (item=b) => { "msg": "b" } PLAY RECAP ****************************************************************************************************************************************************************** 10.1.61.187 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
4. with_together
with_together
可以将两个列表中的元素对齐合并
示例如下:
- hosts: test remote_user: root vars: alpha: [ 'a','b'] numbers: [ 1,2] tasks: - debug: msg="{{ item.0 }} and {{ item.1 }}" with_together: - "{{ alpha }}" - "{{ numbers }}" # 输出的结果为: ok: [10.1.61.187] => (item=['a', 1]) => { "item": [ "a", 1 ], "msg": "a and 1" } ok: [10.1.61.187] => (item=['b', 2]) => { "item": [ "b", 2 ], "msg": "b and 2" }
可以看到第一个列表中的第一个元素a与第二个列表中的第一个元素1合并输出,第一个列表中的b与第二个列表中的第二个元素2合并输出了
上面的示例是基于两个列表的元素完全相同的结果,如果两个列表中的元素不同:
- hosts: test remote_user: root vars: alpha: [ 'a','b','c'] numbers: [ 1,2] tasks: - debug: msg="{{ item.0 }} and {{ item.1 }}" with_together: - "{{ alpha }}" - "{{ numbers }}" # 输出结果: # ansible-playbook with_together_ex.yml PLAY [test] ***************************************************************************************************************************************************************** TASK [debug] **************************************************************************************************************************************************************** ok: [10.1.61.187] => (item=['a', 1]) => { "msg": "a and 1" } ok: [10.1.61.187] => (item=['b', 2]) => { "msg": "b and 2" } ok: [10.1.61.187] => (item=['c', None]) => { "msg": "c and " } PLAY RECAP ****************************************************************************************************************************************************************** 10.1.61.187 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
5. with_nested
嵌套循环
tasks: - name: debug loops debug: msg="name is {{ item[0] }} vaule is {{ item[1] }} " with_nested: - ['alice','bob'] - ['a','b','c']
item[0]是循环的第一个列表的值[‘alice’,’bob’]。item[1]是第二个列表的值;以上的执行输出如下:
# ansible-playbook with_nested_ex.yml PLAY [with_nested test] ******************************************************************************************************************** TASK [debug loops] ************************************************************************************************************************* ok: [10.1.61.187] => (item=['alice', 'a']) => { "msg": "name is alice vaule is a" } ok: [10.1.61.187] => (item=['alice', 'b']) => { "msg": "name is alice vaule is b" } ok: [10.1.61.187] => (item=['alice', 'c']) => { "msg": "name is alice vaule is c" } ok: [10.1.61.187] => (item=['bob', 'a']) => { "msg": "name is bob vaule is a" } ok: [10.1.61.187] => (item=['bob', 'b']) => { "msg": "name is bob vaule is b" } ok: [10.1.61.187] => (item=['bob', 'c']) => { "msg": "name is bob vaule is c" } PLAY RECAP ********************************************************************************************************************************* 10.1.61.187 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
下面是一个稍微有用点儿的示例:
- hosts: test gather_facts: false tasks: - file: state: directory path: "/data/{{ item[0] }}/{{ item[1] }}" with_nested: - [test1,test2] - [a,b,c]
with_cartesian与其功能完全一致
5. with_indexed_items
在循环处理列表时,为列表中的每一项添加索引(从0开始的数字索引)
简单示例:
- hosts: test gather_facts: false tasks: - debug: msg: "{{ item }}" with_indexed_items: - test1 - test2 - test3
执行之后,返回结果如下:
# ansible-playbook with_indexed_items_ex.yml PLAY [test] ******************************************************************************************************************************** TASK [debug] ******************************************************************************************************************************* ok: [10.1.61.187] => (item=[0, 'test1']) => { "msg": [ 0, "test1" ] } ok: [10.1.61.187] => (item=[1, 'test2']) => { "msg": [ 1, "test2" ] } ok: [10.1.61.187] => (item=[2, 'test3']) => { "msg": [ 2, "test3" ] } PLAY RECAP ********************************************************************************************************************************* 10.1.61.187 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
所以我们可以使用with_indexed_items执行如下操作:
- hosts: test gather_facts: false tasks: - debug: msg: "index is {{ item[0] }}, value is {{ item[1] }}" with_indexed_items: - test1 - test2 - test3
下面再看一个稍微复杂的列表结构:
- hosts: test gather_facts: false tasks: - debug: msg: "index is {{ item[0] }}, value is {{ item[1] }}" with_indexed_items: - test1 - [test2,test3] - [test4,test5]
这个时候,返回的结果如下:
# ansible-playbook with_indexed_items_ex2.yml PLAY [test] ******************************************************************************************************************************** TASK [debug] ******************************************************************************************************************************* ok: [10.1.61.187] => (item=[0, 'test1']) => { "msg": "index is 0, value is test1" } ok: [10.1.61.187] => (item=[1, 'test2']) => { "msg": "index is 1, value is test2" } ok: [10.1.61.187] => (item=[2, 'test3']) => { "msg": "index is 2, value is test3" } ok: [10.1.61.187] => (item=[3, 'test4']) => { "msg": "index is 3, value is test4" } ok: [10.1.61.187] => (item=[4, 'test5']) => { "msg": "index is 4, value is test5" } PLAY RECAP ********************************************************************************************************************************* 10.1.61.187 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
可以看到,其在处理更复杂列表的时候,会将列表拉平,类似于with_items
。
与with_items
一样,其也只会拉平第一层列表,如果存在多层列表嵌套,则更深的嵌套不会被拉平:
- hosts: test gather_facts: false tasks: - debug: msg: "index is {{ item[0] }}, value is {{ item[1] }}" with_indexed_items: - test1 - [test2,[test3,test4]]
此时的返回结果:
# ansible-playbook with_indexed_items_ex3.yml PLAY [test] ******************************************************************************************************************************** TASK [debug] ******************************************************************************************************************************* ok: [10.1.61.187] => (item=[0, 'test1']) => { "msg": "index is 0, value is test1" } ok: [10.1.61.187] => (item=[1, 'test2']) => { "msg": "index is 1, value is test2" } ok: [10.1.61.187] => (item=[2, ['test3', 'test4']]) => { "msg": "index is 2, value is ['test3', 'test4']" } PLAY RECAP ********************************************************************************************************************************* 10.1.61.187 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
6. with_sequence
用于返回一个数字序列
参数说明:
start:指定起始值
end:指定结束值
stride:指定步长,即从start至end,每次增加的值
count:生成连续的数字序列,从1开始,到count的值结束
format:格式化输出,类似于linux命令行中的printf格式化输出
关于format参数,更多的格式化输出参数可参考:http://www.zsythink.net/archives/1411
- hosts: test gather_facts: false tasks: # create groups - group: name: {{ item }} state: present with_items: - evens - odds # create some test users # [testuser00,testuser01,testuser02,...,testuser32] - user: name: {{ item }} state: present groups: evens with_sequence: start: 0 end: 32 stride: 4 format: testuser%02d # create a series of directories with even numbers for some reason # [4,6,8,10,...,16] - file: dest: /var/stuff/{{ item }} state: directory with_sequence: start: 4 end: 16 stride: 2 # a simpler way to use the sequence plugin # create 4 groups - group: name: group{{ item }} state: present with_sequence: count=4
7. with_random_choice
用于从一个列表的多个值中随机返回一个值
下面的示例,一个列表当中有四个值,连续执行playbook,每次都随机返回一个:
- hosts: test gather_facts: false tasks: - debug: msg={{ item }} with_random_choice: - "go through the door" - "drink from the goblet" - "press the red button" - "do nothing"
8. with_dict
循环字典
- hosts: test gather_facts: no vars: # 假如有如下变量内容: users: alice: name: Alice Appleworth telephone: 123-456-7890 bob: name: Bob Bananarama telephone: 987-654-3210 # 现在需要输出每个用户的用户名和手机号: tasks: - name: Print phone records debug: msg: "User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})" with_dict: "{{ users }}"
输出如下:
# ansible-playbook with_dict_ex.yml PLAY [test] ****************************************************************************************************************************************************************************************************** TASK [Print phone records] *************************************************************************************************************************************************************************************** ok: [10.1.61.187] => (item={'key': 'alice', 'value': {'name': 'Alice Appleworth', 'telephone': '123-456-7890'}}) => { "msg": "User alice is Alice Appleworth (123-456-7890)" } ok: [10.1.61.187] => (item={'key': 'bob', 'value': {'name': 'Bob Bananarama', 'telephone': '987-654-3210'}}) => { "msg": "User bob is Bob Bananarama (987-654-3210)" } PLAY RECAP ******************************************************************************************************************************************************************************************************* 10.1.61.187 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
9. with_subelement
with_subelement简单来讲,就是在一个复杂的列表当中,可以对这个列表变量的子元素进行遍历
下面是一个简单的示例:
- hosts: test gather_facts: no vars: users: - name: bob hobby: - Games - Sports - name: alice hobby: - Music tasks: - debug: msg: "{{ item }}" with_subelement: - "{{ users }}" - hobby
输出结果如下:
# ansible-playbook with_subelement_ex.yml PLAY [test] ***************************************************************************************************************************************************************** TASK [debug] **************************************************************************************************************************************************************** ok: [10.1.61.187] => (item=[{'name': 'bob'}, 'Games']) => { "msg": [ { "name": "bob" }, "Games" ] } ok: [10.1.61.187] => (item=[{'name': 'bob'}, 'Sports']) => { "msg": [ { "name": "bob" }, "Sports" ] } ok: [10.1.61.187] => (item=[{'name': 'alice'}, 'Music']) => { "msg": [ { "name": "alice" }, "Music" ] } PLAY RECAP ****************************************************************************************************************************************************************** 10.1.61.187 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
可以看到,其按照我们指定的变量users的子项hobby进行了组合输出。with_elementes将hobby子元素的每一项作为一个整体,将其他子元素作为整体,然后组合在一起。
假如现在需要遍历一个用户列表,并创建每个用户,而且还需要为每个用户推送特定的SSH公钥以用于实现远程登录。同时为某一个用户创建独立的mysql登录帐号并为其授权。
示例如下:
- hosts: test gather_facts: false vars: users: - name: alice authorized: - files/keys/master1.id_rsa.pub - files/keys/master2.id_rsa.pub mysql: password: mysql-password hosts: - "%" - "127.0.0.1" - "::1" - "localhost" privs: - "*.*:SELECT" - "DB1.*:ALL" - name: bob authorized: - files/keys/master3.id_rsa.pub mysql: password: other-mysql-password hosts: - "db1" privs: - "*.*:SELECT" - "DB2.*:ALL" tasks: - user: name: "{{ item.name }}" state: present generate_ssh_key: yes with_items: "{{ users }}" - authorized_key: user: "{{ item.0.name }}" key: "{{ lookup('file', item.1) }}" with_subelements: - "{{ users }}" - authorized - name: Setup MySQL users mysql_user: name: "{{ item.0.name }}" password: "{{ item.0.mysql.password }}"" host: "{{ item.1 }} priv={{ item.0.mysql.privs | join('/') }}" with_subelements: - "{{ users }" - mysql.hosts ``` ## 10. with\_file 用于循环主控端的文件列表,获取文件中的内容 > 注意: 循环的是主控端的文件列表,不是被控端的 ```bash - hosts: test gather_facts: false tasks: - debug: msg: {{ item }} with_file: - /etc/ansible/test1.yml - /etc/ansible/test2.yml
输出如下:
# ansible-playbook with_file_ex.yml PLAY [test] ******************************************************************************************************************************** TASK [debug] ******************************************************************************************************************************* ok: [10.1.61.187] => (item=content: test1.yaml) => { "msg": "content: test1.yaml" } ok: [10.1.61.187] => (item=content: test2.yml) => { "msg": "content: test2.yml" } PLAY RECAP ********************************************************************************************************************************* 10.1.61.187 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
11. with_fileglob
上面with_file
用于获取文件的内容,而with_fileglob
则用于匹配文件名称。可以通过该关键字,在指定的目录中匹配符合模式的文件名。与with_file
相同的是,****操作的文件也是主控端的文件而非被控端的文件
- hosts: test tasks: - name: Make key directory file: path: /root/.sshkeys state: directory mode: 0700 owner: root group: root - name: Upload public keys copy: src: "{{ item }}" dest: /root/.sshkeys mode: 0600 owner: root group: root with_fileglob: - /root/.ssh/*.pub - name: Assemble keys into authorized_keys file assemble: src: /root/.sshkeys dest: /root/.ssh/authorized_keys mode: 0600 owner: root group: root
12. with_lines
with_lines循环结构会让你在控制主机上执行任意命令,并对命令的输出进行逐行迭代。
假设我们有一个文件test.txt包含如下行:
Breeze Yan Bernie Yang jerry Qing
我们可以通过如下方法进行逐行输出:
- name: print all names debug: msg="{{ item }}" with_lines: - cat test.txt
13. do-Until循环
- action: shell /usr/bin/foo register: result until: result.stdout.find("all systems go") != -1 retries: 5 delay: 10
重复执行shell模块,当shell模块执行的命令输出内容包含”all systems go”的时候停止。重试5次,延迟时间10秒。retries默认值为3,delay默认值为5。任务的返回值为最后一次循环的返回结果。